package castledefense.logic.units;

import castledefense.logic.Map;

import pathfinding.grid.GridLocation;
import pathfinding.grid.GridPath;

public class Mover{

	protected final int moverType;

	protected double posX;
	protected double posY;
	protected final double size;
	protected double speed;
	
	private final double difX;
	private final double difY;

	protected Destination destination;

	private GridPath path;
	private GridLocation nextLoc;
	private boolean isSearchingPath = true;
	
	protected final Map map;

	protected Mover(double posX, double posY, double size, double speed, int type, Map map) {
		this.posX = posX;
		this.posY = posY;
		this.size = size;
		this.speed = speed;
		this.moverType = type;
		this.map = map;
		double difMax = 1 - size;
		difX = Math.random()*difMax*1.6 - difMax*0.8;
		difY = Math.random()*difMax*1.6 - difMax*0.8;
	}
	
	//For debugging
	public GridPath getPath(){
		if(path != null){
			return path.clone();
		}else{
			return null;
		}
	}
	
	public int getCellX() { return (int)posX; }
	
	public int getCellY() { return (int)posY; }

	public double getCenterX() { return posX; }

	public double getCenterY() { return posY; }

	public double getMinX() { return posX - size/2; }

	public double getMinY() { return posY - size/2; }

	public double getMaxX() { return posX + size/2; }

	public double getMaxY() { return posY + size/2; }

	public double getSize() { return size; }

	public double getSpeed() { return speed; }

	public int getMoverType() { return moverType; }

	public boolean contains(double x, double y) {
		if ((getMinX() <= x && getMaxX() >= x) && (getMinY() <= y && getMaxY() >= y)) {
			return true;
		}
		return false;
	}

	protected void eventPathNotFound() {}

	protected void eventNewDestination() {}
	
	protected void eventCalculateNewPath() {}
	
	protected void eventEndOfPath() {}

	/**
	 * Set the new destination of this mover. If the unit is currently moving it will stop moving.
	 * This method call calculatepath()
	 * 
	 * @param destination
	 */
	public void setDestination(Destination destination) {
		this.destination = destination;
		path = null;
		nextLoc = null;
		calculatePath();
		eventNewDestination();
	}

	/**
	 * If the destination of this mover is not null, it will send a request to the pathfinding thread
	 * to find a path to the current destination.
	 */
	public void calculatePath() {
		if(destination != null){
			isSearchingPath = true;
			path = null;
			nextLoc = null;
			//Send a request to the pathfinding thread to find a path
			map.recalculatePath(this);
		}
		eventCalculateNewPath();
		
	}

	/**
	 * Set the path that this mover must follow. That method is called after the pathfinding thread
	 * find a path to the current destination.
	 * @param path
	 */
	public void setPath(GridPath path) {
		if(path == null){
			eventPathNotFound();
		}else{
			this.path = path;
			isSearchingPath = false;
			destination = new Destination(0, 0, 1);
			//Kinda hacky :S
			//The returned path start at the center of the cell where the unit is located.
			//but that is not where we want to start moving so we get the second location.
			if (path != null && path.hasNextMove()) {
				nextLoc = path.getNextMove();
			}
			if (path != null && path.hasNextMove()) {
				nextLoc = path.getNextMove();
			}
		}
	}


	private int timeLeftToUpdate = 0;
	private final int timeBetweenMove = 10;
	public void updatePath(int deltaTime) {
		
		if(isSearchingPath || destination == null || path == null || nextLoc == null){
			return;
		}

		timeLeftToUpdate += deltaTime;
		while(timeLeftToUpdate > timeBetweenMove){
			
			if(isSearchingPath || destination == null || path == null || nextLoc == null){
				return;
			}
			
			timeLeftToUpdate -= timeBetweenMove;
			move(timeBetweenMove);
		}
	}
	
	private void move(int deltaTime){
		double newPosX;
		double newPosY;

		boolean hasMoved = false;
		
		final double movementFactor = map.getMovementValue(getCellX(), getCellY(), getMoverType());		
		final double movingDistance = (deltaTime*getSpeed()*movementFactor) * (deltaTime*getSpeed()*movementFactor);

		final double startX = getCenterX();
		final double startY = getCenterY();

		while(!hasMoved){

			if(hasCellChanged(nextLoc.getCellX(), nextLoc.getCellY())){
				calculatePath();
				return;
			}

			final double endX = nextLoc.getCenterX()+difX;
			final double endY = nextLoc.getCenterY()+difY;

			final double dx = endX - startX;
			final double dy = endY - startY;
			final double distance = dx*dx + dy*dy;

			
			if(movingDistance >= distance){
				if(!path.hasNextMove()){
					posX = endX;
					posY = endY;
					stopMoving();
					eventEndOfPath();
					hasMoved = true;
				}else{
					nextLoc = path.getNextMove();
					hasMoved = false;
				}
			}else{
				final double value = Math.sqrt(movingDistance / distance);
				final double moveX = dx * value;
				final double moveY = dy * value;
				newPosX = posX + moveX;
				newPosY = posY + moveY;
				if(isValidLocation(newPosX, newPosY)){
					posX = newPosX;
					posY = newPosY;
					hasMoved = true;
				}else{
					calculatePath();
					return;
				}
			}

		}

	}
	
	public void stopMoving() {
		nextLoc = null;
		path = null;
		destination = null;
		isSearchingPath = false;
	}
	
	private boolean hasCellChanged(int x, int y) {
		double movementFactor = map.getMovementValue(getCellX(), getCellY(), getMoverType());
		if(movementFactor == nextLoc.getValue()){
			return false;
		}else{
			return true;
		}
	}
	
	private boolean isValidLocation(double x, double y){
		double value = map.getMovementValue((int)x, (int)y, getMoverType());
		if(value <= 0){
			return false;
		}
		if(x < 0 || x > map.getSizeX() || y < 0 || y > map.getSizeY()){
			return false;
		}
		return true;
	}

}
